package domein;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * File sending class, intended for use by the client intent on sending a file.
 *
 * @author Colin Bundervoet
 * @author Jeroen Pattyn
 */
public class FileSender
{
    private Client client;
    private String initMessage;
    private int port;
    private ServerSocket serverSocket;
    private Socket clientSocket;
    private BufferedOutputStream output;
    private SendingFileObservable sendingFileObservable;

    /**
     * Constructor for the FileSender object.
     *
     * @param client Client to update gui using its observers.
     * @param initMessage the intial message to hand to the server to start
     * filetransfer.
     */
    public FileSender(Client client, String initMessage) {
        this.client = client;
        this.initMessage = initMessage;
    }

    /**
     * Sets the port for the client to connect over.
     *
     * @param port number to connect on.
     * @throws PortOutOfRangeException when invalid port is given.
     */
    private void setPort(int port) throws PortOutOfRangeException {
        if (port > -1 && port < 65536) {
            this.port = port;
        }
        else {
            throw new PortOutOfRangeException(port, 0, 65535);
        }
    }

    /**
     * initiates the file transfer,
     *
     * @param port port to transfer file over
     * @param sendingFileObservable observable to inform gui components
     */
    public void start(int port, SendingFileObservable sendingFileObservable) {
        try {
            this.sendingFileObservable = sendingFileObservable;
            setPort(port);
            serverSocket = new ServerSocket(port);
            port = serverSocket.getLocalPort();
            if (this.port != port) {
                this.port = port;
            }
            sendingFileObservable.update("Sending file initiated.", -1);
        }
        catch (IOException ex) {
            Logger.getLogger(FileSender.class.getName()).log(Level.SEVERE, null, ex);
        }
    }

    /**
     * Sends the actual file over it's own thread.
     *
     * @param file file to transfer
     */
    public void send(final File file) {
        Thread t = new Thread()
        {
            @Override
            public void run() {
                client.send(initMessage + " >" + file.getName() + ">" + file.length() + ">" + port);
                try {
                    sendingFileObservable.update(0);
                    clientSocket = serverSocket.accept();
                    output = new BufferedOutputStream(clientSocket.getOutputStream());
                    sending(file);
                }
                catch (IOException ex) {
                    Logger.getLogger(FileSender.class.getName()).log(Level.SEVERE, null, ex);
                }
            }
        };
        t.start();
    }

    /**
     * intermediate method to send pieces of the file in pieces.
     *
     * @param file file to be sent.
     */
    private void sending(File file) {
        try {
            byte[] mybytearray = new byte[1024];
            BufferedInputStream bis = new BufferedInputStream(new FileInputStream(file));
            int bytesRead = 0;
            while (1024 == (bytesRead = bis.read(mybytearray, 0, mybytearray.length))) {
                output.write(mybytearray, 0, bytesRead);
                output.flush();
            }
            bis.close();
        }
        catch (IOException ex) {
            Logger.getLogger(FileSender.class
                    .getName()).log(Level.SEVERE, null, ex);
        }
        sendingFileObservable.update(100);
        close();
    }

    /**
     * Closes all related sockets and streams. Invoke after file transfer is
     * complete.
     */
    private void close() {
        try {
            sendingFileObservable.update(-1);
            output.close();
            clientSocket.close();
            serverSocket.close();
        }
        catch (IOException ex) {
            Logger.getLogger(FileSender.class
                    .getName()).log(Level.SEVERE, null, ex);
        }
    }
}